package com.zzw.utils;

import sun.misc.Unsafe;

import java.io.*;
import java.lang.management.*;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.zip.CRC32;

public class Utils {

    public static final String EMPTY_STRING = "";
    public static final String NEWLINE = "\r\n";

    public static final String[] EMPTY_STRING_ARRAY = new String[0];
    public static final int[] EMPTY_INT_ARRAY = new int[0];

    private static final String LOCAL_IP_ADDRESS = getLocalInetAddress();
    private static final int PID = doGetCurrrentPid();
    private static final String PID_STR = String.valueOf(PID);

    public static boolean isBlank(String str) {
        int strLen;
        if (str == null || (strLen = str.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; ++i) {
            if (!Character.isWhitespace(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public static String checkNotNullEmpty(String value, String name) throws IllegalArgumentException {
        if (isBlank(value)) {
            throw new IllegalArgumentException(name + " is null or empty");
        }
        return value;
    }

    public static <T> T checkNotNull(T value, String name) throws IllegalArgumentException {
        if (value == null) {
            throw new IllegalArgumentException(name + " is null");
        }
        return value;
    }

    public static <T> T defaultIfNull(T value, T defaultValue) {
        return value == null ? defaultValue : value;
    }

    public static boolean isNotBlank(String str) {
        return !isBlank(str);
    }

    public static boolean isNotEmpty(String str) {
        return str != null && str.length() > 0;
    }

    public static String trim(String str) {
        return str == null ? null : str.trim();
    }

    public static String[] split(String str, char separatorChar) {
        return splitWorker(str, separatorChar, false);
    }

    private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) {
        if (str == null) {
            return null;
        }
        int len = str.length();
        if (len == 0) {
            return EMPTY_STRING_ARRAY;
        }
        List<String> list = new ArrayList<String>();
        int i = 0, start = 0;
        boolean match = false;
        boolean lastMatch = false;
        while (i < len) {
            if (str.charAt(i) == separatorChar) {
                if (match || preserveAllTokens) {
                    list.add(str.substring(start, i));
                    match = false;
                    lastMatch = true;
                }

                start = ++i;
                continue;
            }
            lastMatch = false;
            match = true;
            i++;
        }

        if (match || (preserveAllTokens && lastMatch)) {
            list.add(str.substring(start, i));
        }

        return list.toArray(new String[list.size()]);
    }

    /**
     * <pre>
     *     StringUtils.split(null, *)           = null
     *     StringUtils.split("", *)             = []
     *     StringUtils.split("abc def", null)   = ["abc", "def"]
     *     StringUtils.split("abd def", " ")    = ["abc", "def"]
     *     StringUtils.split("abc  def", " ")   = ["abc", "def"]
     *     StringUtils.split("ab:cd:ef", ":)    = ["ab", "cd", "ef"]
     * </pre>
     *
     * @param str
     * @param separatorChars
     * @return
     */
    public static String[] split(String str, String separatorChars) {
        return splitWorker(str, separatorChars, -1, false);
    }

    // copy from org.apache.commons.lang3.StringUtils#splitWorker(java.lang.String, java.lang.String, int, boolean)
    private static String[] splitWorker(String str, String separatorChars, int max, boolean preserveAllTokens) {
        // Performance tuned for 2.0 (JDK1.4)
        // Direct code is quicker than StringTokenizer.
        // Also, StringTokenizer uses isSpace() not isWhitespace()

        if (str == null) {
            return null;
        }
        int len = str.length();
        if (len == 0) {
            return EMPTY_STRING_ARRAY;
        }
        List<String> list = new ArrayList<String>();
        int sizePlus1 = 1;
        int i = 0, start = 0;
        boolean match = false;
        boolean lastMatch = false;
        if (separatorChars == null) {
            // Null separator means use whitespace
            while (i < len) {
                if (Character.isWhitespace(str.charAt(i))) {
                    if (match || preserveAllTokens) {
                        lastMatch = true;
                        if (sizePlus1++ == max) {
                            i = len;
                            lastMatch = false;
                        }
                        list.add(str.substring(start, i));
                        match = false;
                    }
                    start = ++i;
                    continue;
                }
                lastMatch = false;
                match = true;
                i++;
            }
        } else if (separatorChars.length() == 1) {
            // Optimise 1 character case
            char sep = separatorChars.charAt(0);
            while (i < len) {
                if (str.charAt(i) == sep) {
                    if (match || preserveAllTokens) {
                        lastMatch = true;
                        if (sizePlus1++ == max) {
                            i = len;
                            lastMatch = false;
                        }
                        list.add(str.substring(start, i));
                        match = false;
                    }
                    start = ++i;
                    continue;
                }
                lastMatch = false;
                match = true;
                i++;
            }
        } else {
            // standard case
            while (i < len) {
                if (separatorChars.indexOf(str.charAt(i)) >= 0) {
                    if (match || preserveAllTokens) {
                        lastMatch = true;
                        if (sizePlus1++ == max) {
                            i = len;
                            lastMatch = false;
                        }
                        list.add(str.substring(start, i));
                        match = false;
                    }
                    start = ++i;
                    continue;
                }
                lastMatch = false;
                match = true;
                i++;
            }
        }
        if (match || preserveAllTokens && lastMatch) {
            list.add(str.substring(start, i));
        }
        return list.toArray(new String[list.size()]);
    }

    public static StringBuilder appendWithBlankCheck(String str, String defaultValue, StringBuilder appender) {
        if (isNotBlank(str)) {
            appender.append(str);
        } else {
            appender.append(defaultValue);
        }

        return appender;
    }

    public static StringBuilder appendWithNullCheck(Object obj, String defaultValue, StringBuilder appender) {
        if (obj != null) {
            appender.append(obj.toString());
        } else {
            appender.append(defaultValue);
        }

        return appender;
    }

    /**
     * 追加日志， 同时过滤字符串中的换行为空格， 避免导致日志格式解析错误
     *
     * @param str
     * @param appender
     * @param delimiter
     * @return
     */
    public static StringBuilder appendLog(String str, StringBuilder appender, char delimiter) {
        if (str != null) {
            int len = str.length();
            appender.ensureCapacity(appender.length() + len);

            for (int i = 0; i < len; ++i) {
                char c = str.charAt(i);
                if (c == '\n' || c == '\r' || c == delimiter) {
                    // 用 appender.append(str, start, len) 批量 append 实质也是一个字符一个字符拷贝
                    // 因此此处还是用土办法
                    c = ' ';
                }

                appender.append(c);
            }
        }
        return appender;
    }

    /**
     * 过滤字符串中的换行为空格， 避免导致日志格式解析错误
     *
     * @param str
     * @return
     * @see #appendLog(String, StringBuilder, char)
     */
    public static String filterInvalidCharacters(String str) {
        StringBuilder appender = new StringBuilder(str.length());
        return appendLog(str, appender, '|').toString();
    }

    /**
     * 对字符串生成摘要， 目前使用 CRC32 算法
     *
     * @param str
     * @return
     */
    public static String digest(String str) {
        CRC32 crc = new CRC32();
        crc.update(str.getBytes());
        return Long.toHexString(crc.getValue());
    }

    public static Integer parseIntegerQuietly(String value) {
        if (isBlank(value)) {
            return null;
        }
        boolean negative = value.charAt(0) == '-';
        int index = negative ? 1 : 0;
        if (index == value.length()) {
            return null;
        }
        int digit = value.charAt(index++) - '0';
        if (digit < 0 || digit > 9) {
            return null;
        }
        int accum = -digit;
        while (index < value.length()) {
            digit = value.charAt(index++) - '0';
            if (digit < 0 || digit > 9 || accum < Integer.MIN_VALUE / 10) {
                return null;
            }

            accum *= 10;
            if (accum < Integer.MIN_VALUE + digit) {
                return null;
            }
            accum -= digit;
        }

        if (negative) {
            return accum;
        } else if (accum == Integer.MIN_VALUE) {
            return null;
        } else {
            return -accum;
        }
    }

    public static int parseIntQuietly(String value, int defaultValue, int beginPos, int endPos) {
        if (value == null || beginPos < 0 || beginPos >= endPos || endPos > value.length()) {
            return defaultValue;
        }
        boolean negative = value.charAt(beginPos) == '-';
        int index = negative ? beginPos + 1 : beginPos;
        if (index == endPos) {
            return defaultValue;
        }
        int digit = value.charAt(index++) - '0';
        if (digit < 0 || digit > 9) {
            return defaultValue;
        }
        int accum = -digit;
        while (index < endPos) {
            digit = value.charAt(index++) - '0';
            if (digit < 0 || digit > 9 || accum < Integer.MIN_VALUE / 10) {
                return defaultValue;
            }

            accum *= 10;
            if (accum < Integer.MIN_VALUE + digit) {
                return defaultValue;
            }
            accum -= digit;
        }

        if (negative) {
            return accum;
        } else if (accum == Integer.MIN_VALUE) {
            return defaultValue;
        } else {
            return -accum;
        }
    }

    public static int parseIntQuietly(String value, int defaultValue) {
        return value == null ? defaultValue : parseIntQuietly(value, defaultValue, 0, value.length());
    }

    public static int parseIntQuietly(Object value, int defaultValue) {
        return value == null ? defaultValue : (value instanceof Number ? ((Number) value).intValue() : parseIntQuietly(value.toString(), defaultValue));
    }

    public static Boolean parseBooleanQuietly(String value) {
        return "true".equalsIgnoreCase(value) ? Boolean.TRUE : ("false".equalsIgnoreCase(value) ? Boolean.FALSE : null);
    }

    public static boolean parseBooleanQuietly(String value, boolean defaultValue) {
        return defaultValue ? !"false".equalsIgnoreCase(value) : "true".equalsIgnoreCase(value);
    }

    /**
     * 从 URL 解析出 URI， 如果解析不到 URI， 就用域名， 例如：
     * <pre>
     *     "http://aaaa.xxxx.com/bbbb/cccc.htm" => "/bbbb/cccc.htm"
     *     "http://aaaa.xxxx.com/bbbb/" => "/bbbb"
     *     "http://www.xxxx.com" => "www.xxxx.com"
     *     "http://www.xxxx.com/" => "www.xxxx.com"
     *     "https://www.xxxx.com/" => "www.xxxx.com"
     *     "not_url_entrance" => "not_url_entrance"
     * </pre>
     *
     * @param url
     * @return
     */
    public static String getUriFromUrl(String url) {
        int start;
        int len = url.length();
        if (len <= 7) {
            return url;
        }
        if (url.startsWith("http://")) {
            start = 7;
        } else if ((start = url.indexOf("://")) != -1) {
            start += 3;
        } else {
            start = 0;
        }

        // 去掉末尾的 '/'
        int end = url.charAt(len - 1) == '/' ? len - 1 : len;
        int istart = url.indexOf('/', start);
        return istart >= 0 && istart < end ? url.substring(istart, end) : url.substring(start, end);

    }

    private static String getLocalInetAddress() {
        String localIp = getSystemProperty("CLOTHO.LOCAL.IP");
        if (isNotBlank(localIp) && localIp.length() >= 7 && Character.isDigit(localIp.charAt(0)) && Character.isDigit(localIp.charAt(localIp.length() - 1))) {
            return localIp;
        }
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            InetAddress address = null;
            while (interfaces.hasMoreElements()) {
                NetworkInterface ni = interfaces.nextElement();
                // 排除掉虚拟网卡的IP
                String displayName = ni.getDisplayName();
                if (displayName != null && displayName.startsWith("virbr")) {
                    continue;
                }
                Enumeration<InetAddress> addresses = ni.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    address = addresses.nextElement();
                    if (!address.isLoopbackAddress() && address.getHostAddress().indexOf(":") == -1) {
                        return address.getHostAddress();
                    }
                }
            }
        } catch (Throwable t) {
        }
        return "127.0.0.1";
    }

    public static String getLocalAddress() {
        return LOCAL_IP_ADDRESS;
    }

    /**
     * 根据 TraceId 检查是否被采样， 有效范围在 [1, 9999] 直接， 超出范围的数值都作为全采样处理
     *
     * @param traceId
     * @param samplingInterval 采样频率 1/x, 有效范围在 [1, 9999] 之间， 超出范围的数值都作为全采样处理
     * @return
     */
    public static boolean isTraceSampled(String traceId, int samplingInterval) {
        if (traceId == null) {
            return false;
        }
        if (samplingInterval <= 1 || samplingInterval >= 10000) {
            return true;
        }
        int len = traceId.length();
        if (len < 25) {
            return traceId.hashCode() % samplingInterval == 0;
        }

        if (len >= 26 && traceId.charAt(25) == 'f') {
            return true;
        }
        int count = traceId.charAt(21) - '0';
        count = count * 10 + traceId.charAt(22) - '0';
        count = count * 10 + traceId.charAt(23) - '0';
        count = count * 10 + traceId.charAt(24) - '0';
        return count % samplingInterval == 0;
    }

    /**
     * get current pid, max pid 32 bit systems 32768, for 64 bit 4194304
     * http://unix.stackexchange.com/questions/16883/what-is-the-maximum-value-of-the-pid-of-a-process
     * <p>
     * http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id
     *
     * @return
     */
    private static int doGetCurrrentPid() {
        // 优先获取应用自己配置的 PID
        String pidStr = getSystemProperty("CLOTHO.LOCAL.PID");
        if (isNotBlank(pidStr) && isNumeric(pidStr)) {
            try {
                long pidLong = Long.parseLong(pidStr);
                return Math.abs((int) pidLong);
            } catch (Throwable t) {
                // quietly
            }
        }

        try {
            RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
            String name = runtime.getName();
            return Integer.parseInt(name.substring(0, name.indexOf('@')));
        } catch (Throwable t) {
            return 0;
        }
    }

    public static int getCurrrentPid() {
        return PID;
    }

    public static String getCurrrentPidString() {
        return PID_STR;
    }

    public static String getSystemProperty(String key) {
        try {
            return System.getProperty(key);
        } catch (Throwable t) {
            return null;
        }
    }

    public static long getSystemPropertyForLong(String key, long defaultValue) {
        try {
            return Long.parseLong(System.getProperty(key));
        } catch (Throwable t) {
            return defaultValue;
        }
    }

    /**
     * 验证 traceId 是否合法
     *
     * @param traceId
     * @return
     */
    public static boolean isValidTraceId(String traceId) {
        int len;
        if (traceId == null || (len = traceId.length()) == 0) {
            return false;
        }
        if (len < 25) {
            return handleOldVersion(traceId);
        }

        if (len > 48) {
            return false;
        }
        for (int i = 0; i < 8; ++i) {
            if (!isHexNumeric(traceId.charAt(i))) {
                return false;
            }
        }

        char firstTimeChar = traceId.charAt(8);
        if (firstTimeChar < '1' || firstTimeChar > '3') {
            // 时间戳判断能用 102 年的范围
            return false;
        }
        for (int i = 9; i < 25; ++i) {
            if (!isNumeric(traceId.charAt(i))) {
                return false;
            }
        }

        for (int i = 25; i < len; ++i) {
            if (!isHexNumeric(traceId.charAt(i))) {
                return false;
            }
        }

        return true;
    }

    private static boolean handleOldVersion(String traceId) {
        if (traceId.charAt(0) == '0') {
            return false;
        }
        for (int i = 0; i < traceId.length(); ++i) {
            if (!isHexNumeric(traceId.charAt(i))) {
                return false;
            }
        }

        return true;
    }

    public static boolean isValidRpcId(String rpcId) {
        int len;
        if (rpcId == null || (len = rpcId.length()) == 0) {
            return false;
        }
        if (len > 64) {
            return false;
        }
        if (!isNumeric(rpcId.charAt(0))) {
            return false;
        }
        for (int i = 1; i < len; ++i) {
            char ch = rpcId.charAt(i);
            if (!isNumeric(ch) && ch != '.') {
                return false;
            }
        }

        return true;
    }

    public static boolean isHexNumeric(char ch) {
        return ch >= 'a' && ch <= 'f' || ch >= '0' && ch <= '9';
    }

    public static boolean isNumeric(char ch) {
        return ch >= '0' && ch <= '9';
    }

    public static boolean isNumeric(String str) {
        if (isBlank(str)) {
            return false;
        }
        int sz = str.length();

        for (int i = 0; i < sz; ++i) {
            if (!isNumeric(str.charAt(i))) {
                return false;
            }
        }

        return true;
    }

    public static boolean isInRangeInclude(int num, int min, int max) {
        return num >= min && num <= max;
    }

    /**
     * 删除 file， 如果是目录， 会先递归删除目录内的文件，再删除目录本身
     *
     * @param file
     * @return
     */
    public static boolean deleteQuietly(File file) {
        if (file == null) {
            return false;
        }
        try {
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files != null) {
                    for (File f : files) {
                        deleteQuietly(f);
                    }
                }
            }
        } catch (Exception ignored) {
            // quietly
        }

        try {
            return file.delete();
        } catch (Exception ignored) {
            return false;
        }
    }

    /**
     * 读取文件的第一行内容
     *
     * @param file
     * @return
     */
    public static String readFirstLineFromFile(File file) {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(file), 128);
            return br.readLine();
        } catch (Exception e) {
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    // quietly
                }
            }

        }

        return null;
    }

    /**
     * 按行读取文件内容
     *
     * @param file
     * @return 如果读取异常， 返回 <code>null</code>, 否则返回一个 List
     */
    public static List<String> readLinesFromFile(File file) {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(file), 128);

            String line;
            List<String> lines = new ArrayList<String>();
            while ((line = br.readLine()) != null) {
                lines.add(line);
            }

            return lines;
        } catch (Exception e) {
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    // quietly
                }
            }
        }

        return null;
    }

    public static void shutdownThreadPool(ExecutorService pool, long awaitTimeMillis) {
        try {
            pool.shutdown();

            boolean done = false;
            if (awaitTimeMillis > 0) {
                try {
                    done = pool.awaitTermination(awaitTimeMillis, TimeUnit.MILLISECONDS);
                } catch (Exception e) {
                }
            }

            if (!done) {
                pool.shutdownNow();
            }
        } catch (Exception var6) {
            // quietly
        }
    }

    public static String toString(Throwable t) {
        StringWriter sw = new StringWriter(4096);
        PrintWriter pw = new PrintWriter(sw, false);
        t.printStackTrace(pw);
        pw.flush();
        return sw.toString();
    }

    public static void printMemPoolUsage(String tag, OutputStream out) {
        List<MemoryPoolMXBean> memoryPools = ManagementFactory.getMemoryPoolMXBeans();
        for (MemoryPoolMXBean memoryPool : memoryPools) {
            if (memoryPool == null) continue;
            MemoryUsage usage = memoryPool.getUsage();
            if (usage == null) continue;

            String poolName = memoryPool.getName();
            String message = tag + " " + poolName + ": init=" + toMbSize(usage.getInit())
                    + ", used=" + toMbSize(usage.getUsed()) + ", committed=" + toMbSize(usage.getCommitted())
                    + ", max=" + toMbSize(usage.getMax()) + NEWLINE;
            try {
                out.write(message.getBytes());
            } catch (IOException e) {
                // quietly
            }
        }
    }

    private static final String toMbSize(long value) {
        return value / 1024L / 1024L + "MB";
    }

    public static String getClassLocation(Class<?> clazz) {
        try {
            URL resource = clazz.getProtectionDomain().getCodeSource().getLocation();
            if (resource != null) {
                return resource.toString();
            }
        } catch (Throwable t) {
            // ignore
        }

        return "--<unknown location>--";
    }

    @SuppressWarnings("restriction")
    private static final Unsafe UNSAFE = doGetUnsafe();

    @SuppressWarnings("restriction")
    public static Unsafe getUnsafe() {
        return UNSAFE;
    }

    /**
     * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package.
     * Replace with a simple call to Unsafe.getUnsafe when integrating
     * into a jdk.
     *
     * @return
     */
    @SuppressWarnings("restriction")
    private static Unsafe doGetUnsafe() {
        try {
            return Unsafe.getUnsafe();
        } catch (Throwable tryReflectionInstead) {
        }
        try {
            return (Unsafe) AccessController.doPrivileged(new PrivilegedExceptionAction() {
                public Unsafe run() throws Exception {
                    Class<Unsafe> k = Unsafe.class;
                    for (Field f : k.getDeclaredFields()) {
                        f.setAccessible(true);
                        Object x = f.get(null);
                        if (k.isInstance(x)) {
                            return k.cast(x);
                        }
                    }
                    throw new NoSuchFieldError("the Unsafe");
                }
            });
        } catch (Throwable t) {
            return null;
        }
    }

    private static final ThreadMXBean threadMXBean = getThreadMXBean0();

    private static ThreadMXBean getThreadMXBean0() {
        try {
            ThreadMXBean instance = ManagementFactory.getThreadMXBean();
            if (instance != null
                    && instance.isCurrentThreadCpuTimeSupported()
                    && instance.isThreadCpuTimeEnabled()
                    && instance.getCurrentThreadCpuTime() != -1) {
                return instance;
            }
        } catch (Throwable t) {
            // quietly
        }

        // 没有正常取得时， 直接设置成 null， 以后不再尝试
        return null;
    }

    static String getRootDir() {
        String osName = System.getProperty("os.name");
        if (osName != null && osName.toLowerCase().contains("windows")) {
            return System.getProperty("user.home");
        }
        return "";
    }

    /**
     * 获取 ThreadMXBean, 如果没有， 返回 <code>null</code>
     *
     * @return
     */
    public static ThreadMXBean getThreadMXBean() {
        return threadMXBean;
    }
}
